package com.bluemobi.framework.pay.wechat.common;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.bluemobi.framework.pay.wechat.entity.UnifiedOrderReq;
import com.bluemobi.framework.pay.wechat.entity.UnifiedOrderRes;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * 支付工具类
 *
 * @author zhangyong
 * @version 1.0
 * @Date 2016-4-25 16:02
 */
public class PayCommonUtil {

	private static Logger logger = LoggerFactory.getLogger(PayCommonUtil.class);

	private final static String[] HEXDIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
			"e", "f" };

	private final static String NONCE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	/**
	 * 获取支付对象
	 */
	public static UnifiedOrderReq getUnifiedOrderReq(String orderNo, String body, String notify_url,
			String spbill_create_ip, Integer total_fee) {
		UnifiedOrderReq orderReq = new UnifiedOrderReq();
		orderReq.setKey(Configure.APIKEY);
		orderReq.setMch_id(Configure.MCH_ID);
		orderReq.setAppid(Configure.APPID);
		orderReq.setOut_trade_no(orderNo);
		orderReq.setBody(body);
		orderReq.setNotify_url(notify_url);
		orderReq.setSpbill_create_ip(spbill_create_ip);
		orderReq.setTotal_fee(total_fee);
		return orderReq;
	}

	/**
	 * 获取支付对象
	 */
	public static UnifiedOrderReq getUnifiedOrderReq(String openId, String orderNo, String body, String notify_url,
			String spbill_create_ip, Integer total_fee) {
		logger.error("我的支付测试:" + notify_url);
		UnifiedOrderReq orderReq = new UnifiedOrderReq();
		orderReq.setKey(Configure.APIKEY);
		orderReq.setMch_id(Configure.WECHAT_MCH_ID);
		orderReq.setAppid(Configure.WECHAT_APPID);
		orderReq.setOut_trade_no(orderNo);
		orderReq.setBody(body);
		orderReq.setOpenid(openId);
		orderReq.setNotify_url(notify_url);
		orderReq.setSpbill_create_ip(spbill_create_ip);
		orderReq.setTotal_fee(total_fee);
		return orderReq;
	}

	/**
	 * 转换字节数组为16进制字串
	 *
	 * @param b
	 *            字节数组
	 * @return 16进制字串
	 */
	public static String byteArrayToHexString(byte[] b) {
		StringBuilder resultSb = new StringBuilder();
		for (byte aB : b) {
			resultSb.append(byteToHexString(aB));
		}
		return resultSb.toString();
	}

	/**
	 * 转换byte到16进制
	 *
	 * @param b
	 *            要转换的byte
	 * @return 16进制格式
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return HEXDIGITS[d1] + HEXDIGITS[d2];
	}

	/**
	 * MD5编码
	 *
	 * @param origin
	 *            原始字符串
	 * @return 经过MD5加密之后的结果
	 */
	public static String MD5Encode(String origin) {
		String resultString = null;
		try {
			resultString = origin;
			MessageDigest md = MessageDigest.getInstance("MD5");
			resultString = byteArrayToHexString(md.digest(resultString.getBytes(Configure.ENCODING)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultString;
	}

	/**
	 * 活得16位长度的随机码
	 *
	 * @return 随机码
	 */
	public static String getNoncestr() {
		String res = "";
		for (int i = 0; i < 16; i++) {
			Random rd = new Random();
			res += NONCE_CHARS.charAt(rd.nextInt(NONCE_CHARS.length() - 1));
		}
		return res;
	}

	/**
	 * 根据对象返回签名
	 *
	 * @param object
	 *            要参与签名的数据对象
	 * @return 签名
	 */
	public static String getSign(Object object) throws IllegalAccessException {
		ArrayList<String> list = new ArrayList<>();
		Class cls = object.getClass();
		Field[] fields = cls.getDeclaredFields();
		String key = "";// 支付key
		for (Field f : fields) {
			f.setAccessible(true);
			if (f.getName().equals("key")) {
				if (null == f.get(object)) {
					logger.error("签名时未找到加密key");
					return "";
				}
				key = f.get(object).toString();
			} else {
				if (!f.getName().equals("sign") && !f.getName().equals("key")) {
					if (f.get(object) != null && f.get(object) != "") {
						list.add(f.getName() + "=" + f.get(object) + "&");
					}
				}
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + key;
		logger.info("加密前 签名:" + result);
		result = MD5Encode(result).toUpperCase();
		logger.info("加密后 签名:" + result);
		return result;
	}

	/**
	 * 根据对象返回签名
	 *
	 * @param map
	 *            要参与签名的map数据
	 * @return 签名
	 */
	public static String getSign(Map<String, Object> map) {
		String key;// 支付key
		if (null == map.get("key")) {
			logger.error("未获取到支付key");
			return "";
		} else {
			key = map.get("key").toString();
		}
		ArrayList<String> list = new ArrayList<>();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			if (entry.getValue() != "" && !"sign".equals(entry.getKey()) && !"key".equals(entry.getKey())) {
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + key;
		logger.info("加密前 签名:" + result);
		result = PayCommonUtil.MD5Encode(result).toUpperCase();
		logger.info("加密后 签名:" + result);
		return result;
	}

	/**
	 * 将请求对象转换格式的string
	 *
	 * @param object
	 *            请求参数
	 * @return string
	 */
	public static String getRequestXml(Object object) throws IllegalAccessException {
		StringBuilder sb = new StringBuilder();
		sb.append("<xml>");
		Class cls = object.getClass();
		Field[] fields = cls.getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			if (field.get(object) != null && field.get(object) != "") {
				sb.append("<").append(field.getName()).append(">").append(field.get(object)).append("</")
						.append(field.getName()).append(">");
			}
		}
		sb.append("</xml>");
		return sb.toString();
	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 *
	 * @param map
	 *            API返回的XML数据封装的map
	 * @return API签名是否合法
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public static boolean checkIsSignValidFromResponseMap(Map<String, Object> map)
			throws ParserConfigurationException, IOException, SAXException {

		logger.info("回调数据:" + map.toString());
		String signFromAPIResponse = map.get("sign").toString();
		if (!StringUtils.isNotEmpty(signFromAPIResponse)) {
			logger.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		map.put("key", Configure.APIKEY);
		String signForAPIResponse = getSign(map);
		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			logger.error("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		return true;
	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 *
	 * @param map
	 *            API返回的XML数据封装的map
	 * @return API签名是否合法
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public static boolean checkIsSignValidFromWebResponseMap(Map<String, Object> map)
			throws ParserConfigurationException, IOException, SAXException {

		logger.info("回调数据:" + map.toString());
		String signFromAPIResponse = map.get("sign").toString();
		if (!StringUtils.isNotEmpty(signFromAPIResponse)) {
			logger.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		map.put("key", Configure.APIKEY);
		String signForAPIResponse = getSign(map);
		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			logger.error("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		return true;
	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 *
	 * @param responseString
	 *            API返回的XML数据字符串
	 * @return API签名是否合法
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public static boolean checkIsSignValidFromResponseString(String responseString, String key)
			throws ParserConfigurationException, IOException, SAXException {
		Map<String, Object> map = getMapFromXML(responseString);
		logger.info("回调数据:" + map.toString());
		String signFromAPIResponse = map.get("sign").toString();
		if (!StringUtils.isNotEmpty(signFromAPIResponse)) {
			logger.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		map.put("key", key);
		String signForAPIResponse = getSign(map);
		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			logger.error("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		return true;
	}

	public static boolean checkIsSignValidFromResponseStrings(UnifiedOrderRes res, String key)
			throws ParserConfigurationException, IOException, SAXException, IllegalAccessException {

		String signFromAPIResponse = res.getSign();
		// Map<String, Object> map = getMapFromXML(responseString);
		// logger.info("回调数据:" + map.toString());
		// String signFromAPIResponse = map.get("sign").toString();
		// if (!StringUtils.isNotEmpty(signFromAPIResponse)) {
		// logger.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
		// return false;
		// }
		// //将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		// map.put("key", key);
		res.setKey(key);
		String signForAPIResponse = getSign(res);
		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			logger.error("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		return true;
	}

	/**
	 * 将字符串转换成InputStream
	 *
	 * @param sInputString
	 *            字符串
	 * @return tInputStringStream
	 */
	public static InputStream getStringStream(String sInputString) {
		ByteArrayInputStream tInputStringStream = null;
		if (StringUtils.isNotEmpty(sInputString)) {
			tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
		}
		return tInputStringStream;
	}

	/**
	 * 将XML字符串转换为MAP
	 *
	 * @param xmlString
	 *            字符串
	 * @return map
	 */
	public static Map<String, Object> getMapFromXML(String xmlString)
			throws ParserConfigurationException, IOException, SAXException {
		// 这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		InputStream is = getStringStream(xmlString);
		Document document = builder.parse(is);

		// 获取到document里面的全部结点
		NodeList allNodes = document.getFirstChild().getChildNodes();
		Node node;
		Map<String, Object> map = new HashMap<>();
		int i = 0;
		while (i < allNodes.getLength()) {
			node = allNodes.item(i);
			if (node instanceof Element) {
				// map.put(node.getNodeName(), node.getTextContent());
				map.put(node.getNodeName(), node.getNodeValue());
			}
			i++;
		}
		return map;

	}

	/**
	 * XML数据映射到Java对象
	 *
	 * @param xml
	 *            xml数据
	 * @param clazz
	 *            映射类
	 * @return 映射对象
	 */
	public static <T> T getObjectFromXML(String xml, Class<T> clazz) {
		// 将从API返回的XML数据映射到Java对象
		XStream xStreamForResponseData = new XStream(new DomDriver());
		xStreamForResponseData.alias("xml", clazz);
		xStreamForResponseData.ignoreUnknownElements();// 暂时忽略掉一些新增的字段
		return (T) xStreamForResponseData.fromXML(xml);
	}

	/**
	 * 解析微信发来的请求（XML）
	 *
	 * @param request
	 * @return
	 * @throws Exception
	 */
	public static Map<String, Object> parseXml(HttpServletRequest request) throws Exception {
		// 将解析结果存储在HashMap中
		Map<String, Object> map = new HashMap<>();
		// 从request中取得输入流
		InputStream inputStream = request.getInputStream();
		String postData = IOUtils.toString(inputStream, "UTF-8");
		if (StringUtils.isNotEmpty(postData)) {// 保证有数据
			// 读取输入流
			SAXReader reader = new SAXReader();
			org.dom4j.Document document = reader.read(new ByteArrayInputStream(postData.getBytes("UTF-8")));
			// 得到xml根元素
			org.dom4j.Element root = document.getRootElement();
			// 得到根元素的所有子节点
			List<org.dom4j.Element> elementList = root.elements();
			// 遍历所有子节点
			for (org.dom4j.Element e : elementList)
				map.put(e.getName(), e.getText());
			// 释放资源
			inputStream.close();
			inputStream = null;
			return map;
		}
		return null;
	}
}
